﻿
#include "bb_api.h"
#include <chrono>
#include <inttypes.h>
#include <iostream>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <thread>

#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
#include <string>

using namespace std;
using namespace std::chrono_literals;

void disp_bb_status(unsigned char* buff, int len)
{
#if 0
    bb_get_status_out_t  out;

    int sz = sizeof(bb_get_status_out_t);
    memcpy(&out, buff, sz);
    printf("dev role = %d\n", out.role);
#else
    bb_get_status_out_t* pout = (bb_get_status_out_t*)buff;

    printf("dev role = %d\n", pout->role);
#endif
}

int normal_test(bb_dev_handle_t* pdh)
{
    unsigned char input_buff[20] = { 1 };

    unsigned char output_buff[1024];

    for (int i = 0; i < 100; i++) {
        int  len      = 1024;
        auto tp_start = std::chrono::system_clock::now();
        int  ret      = bb_ioctl(pdh, BB_GET_STATUS, input_buff, output_buff);
        if (ret == 0) {
            disp_bb_status(output_buff, len);
        }
        auto tp_end = std::chrono::system_clock::now();
        auto ns     = std::chrono::duration_cast<std::chrono::nanoseconds>(tp_end - tp_start).count();
        printf("time = %" PRIi64 "\n", (int64_t)ns);
        printf("ret = %d\n", ret);
    }

    return 0;
}

void test_bbsocket_query(int fd)
{
    while (true) {
        this_thread::sleep_for(1s);
        QUERY_TX_OUT dat;
        int          ret = bb_socket_ioctl(fd, BB_SOCK_QUERY_TX_BUFF_LEN, &dat);
        printf("tx get data = %" PRIi64 " , buffer len = %d\n", dat.buff_index, dat.buff_len);

        QUERY_RX_OUT datrx;
        ret = bb_socket_ioctl(fd, BB_SOCK_QUERY_RX_BUFF_LEN, &datrx);
        printf("rx get buffer len = %d\n", datrx.buff_len);
    }
}

void test_io_bb_query(bb_dev_handle_t* pdh)
{
    while (true) {
        this_thread::sleep_for(1s);
        QUERY_TX_IN  in;
        QUERY_TX_OUT out;

        in.slot = BB_SLOT_0;
        in.port = 1;

        int ret = bb_ioctl(pdh, BB_RPC_SOCK_BUF_STA, &in, &out);
        printf("BB_RPC_SOCK_BUF_STA index %" PRIi64 " %d\n", out.buff_index, out.buff_len);
    }
}

int socket_speed_test(bb_dev_handle_t* pdh)
{
    int fd = bb_socket_open(pdh, BB_SLOT_0, 1, BB_SOCK_FLAG_RX | BB_SOCK_FLAG_TX, NULL);

    if (fd < 0) {
        printf("open socket err!\n");
        return -1;
    } else {
        printf("open socket ok!\n");
    }
    std::thread qry(test_bbsocket_query, fd);
    qry.detach();
    std::thread qry2(test_io_bb_query, pdh);
    qry2.detach();
#define PACK_SZ  (200 * 1024)
#define PACK_CNT (10000)

    char pstr[PACK_SZ];
    for (int i = 0; i < PACK_SZ; i++) {
        pstr[i] = i % 10 + '0';
    }
    auto tp_start = std::chrono::system_clock::now();

    for (int i = 0; i < PACK_CNT; i++) {
        printf("i = %d write start len = %d\n", i, PACK_SZ);
        int ret = bb_socket_write(fd, (void*)pstr, PACK_SZ, -1);
        printf("i = %d write len = %d\n", i, ret);
    }
    auto  tp_end = std::chrono::system_clock::now();
    auto  ns     = std::chrono::duration_cast<std::chrono::nanoseconds>(tp_end - tp_start).count();
    float speed  = PACK_SZ * PACK_CNT / 1024.0f / 1024 / ns * 1000 * 1000 * 1000 * 8;

    printf("time = %" PRIi64 " us , speed = %f mbps\n", (int64_t)ns / 1000, speed);

    return 0;
}

void bb_event_link(void* arg, void* usr)
{
    bb_event_link_state_t* link = (bb_event_link_state_t*)arg;
    printf("slot = %d , cur = %d , prev = %d\n", link->slot, link->cur_state, link->prev_state);
}

void bb_event_mcs(void* arg, void* usr)
{
    printf("get mcs cb\n");
}

void bb_event_chan(void* arg, void* usr)
{
    printf("get chan cb\n");
}

void bb_event_offline(void* arg, void* usr)
{
    printf("dev offline cb\n");
}

static int set_cb(bb_dev_handle_t* pdh, int evt, bb_event_callback pcb, void* priv)
{
    bb_set_event_callback_t cb;
    cb.callback = pcb;
    cb.event    = (bb_event_e)evt;
    cb.user     = priv;

    int ret = bb_ioctl(pdh, BB_SET_EVENT_SUBSCRIBE, &cb, 0);

    if (ret) {
        printf("set callback err!\n");
        return -1;
    }
    printf("set evt %d callback ok!\n", evt);

    return 0;
}

static void plot_callback(void* arg, void* user)
{
    bb_event_plot_data_t* data = (bb_event_plot_data_t*)arg;

    printf("user %u:\n", data->user);
    for (int i = 0; i < data->plot_num; i++) {
        bb_plot_data_t* item = &data->plot_data[i];
        printf("SNR: %u  LDPC: %u/%u GAIN:(%u, %u) MCS: %d FCH: %u\n",
               item->snr,
               item->ldpc_err,
               item->ldpc_num,
               item->gain_a,
               item->gain_b,
               item->mcs_rx,
               item->fch_lock);
    }
}

/**
 * @brief plot 测试
 *
 * @param pdh
 */
static void set_plot(bb_dev_handle_t* pdh)
{
    bb_set_plot_t plot;
    plot.user      = BB_USER_0;
    plot.enable    = 1;
    plot.cache_num = 3;
    bb_ioctl(pdh, BB_SET_PLOT, &plot, NULL);

    bb_set_event_callback_t callback;
    callback.event    = BB_EVENT_PLOT_DATA;
    callback.callback = plot_callback;
    callback.user     = 0;
    bb_ioctl(pdh, BB_SET_EVENT_SUBSCRIBE, &callback, NULL);
}

int callback_test(bb_dev_handle_t* pdh)
{
    set_cb(pdh, BB_EVENT_LINK_STATE, bb_event_link, NULL);
    set_cb(pdh, BB_EVENT_MCS_CHANGE, bb_event_mcs, NULL);
    set_cb(pdh, BB_EVENT_CHAN_CHANGE, bb_event_chan, NULL);
    set_cb(pdh, BB_EVENT_OFFLINE, bb_event_offline, NULL);

    return 0;
}

int socket_echo_test_client(bb_dev_handle_t* pdh)
{
    int fd = bb_socket_open(pdh, BB_SLOT_0, 1, BB_SOCK_FLAG_RX | BB_SOCK_FLAG_TX, NULL);

    if (fd < 0) {
        printf("open socket err!\n");
        return -1;
    }

#define TEST_SZ (100)

    char sendbuf[TEST_SZ];
    for (int i = 0; i < TEST_SZ; i++) {
        sendbuf[i] = i % 10 + '0';
    }
    auto tp_start = std::chrono::system_clock::now();

    int ret = bb_socket_write(fd, (void*)sendbuf, TEST_SZ, -1);
    if (ret <= 0) {
        printf("write err = %d\n", ret);
        return -1;
    }

    char recvbuf[TEST_SZ];
    ret = bb_socket_read(fd, (void*)recvbuf, TEST_SZ, -1);
    printf("get echo data len = %d\n", ret);

    auto tp_end = std::chrono::system_clock::now();
    auto ns     = std::chrono::duration_cast<std::chrono::nanoseconds>(tp_end - tp_start).count();

    printf("echo time = %" PRIi64 " ms\n", (int64_t)ns / 1000 / 1000);

    bb_socket_close(fd);
    return 0;
}

int socket_echo_test_server(bb_dev_handle_t* pdev)
{
    int fd = bb_socket_open(pdev, BB_SLOT_0, 1, BB_SOCK_FLAG_RX | BB_SOCK_FLAG_TX, NULL);

    if (fd < 0) {
        printf("open socket err!\n");
        return -1;
    }
    while (true) {
#define TEST_SZ_S (10 * 1024)
        char testbuff[TEST_SZ_S];

        int ret = bb_socket_read(fd, (void*)testbuff, TEST_SZ_S, -1);
        if (ret <= 0) {
            break;
        }

        ret = bb_socket_write(fd, (void*)testbuff, ret, -1);
    }
    bb_socket_close(fd);
    return 0;
}

void test_bb_get_status(bb_dev_handle_t* pdh)
{
    bb_get_status_in_t  get_status_in;
    bb_get_status_out_t get_status_out;
    get_status_in.user_bmp = 0x3fff;

    int ret = bb_ioctl(pdh, BB_GET_STATUS, &get_status_in, &get_status_out);

    printf("ret = %d\n", ret);
}

static uint8_t xor_check(unsigned char* buff, int len)
{
    uint8_t xors = 0xff;
    for (int i = 0; i < len; i++) {
        xors ^= buff[i];
    }
    return xors;
}

void datagram_test_client(bb_dev_handle_t* pdh)
{
    int      packlen = 0;
    uint8_t* buff    = (uint8_t*)malloc(1024 * 1024);

    std::random_device                 r;
    std::default_random_engine         e1(r());
    std::uniform_int_distribution<int> uniform_dist(0, 255);

    int fd = bb_socket_open(pdh, BB_SLOT_0, 1, BB_SOCK_FLAG_RX | BB_SOCK_FLAG_TX | BB_SOCK_FLAG_DATAGRAM, NULL);

    while (1) {
        if (++packlen > 1024 * 1024) {
            packlen = 1;
        }

        for (int i = 0; i < packlen; i++) {
            buff[i] = uniform_dist(e1);
        }

        int wr = bb_socket_write(fd, buff, packlen, -1);

        if (wr != packlen) {
            throw "wr len";
        }
        uint8_t xors = xor_check(buff, packlen);

        int rd = bb_socket_read(fd, buff, 1000, -1);

        if (rd != 5) {
            throw "rd len";
        }

        int32_t retlen;
        memcpy(&retlen, buff, 4);
        uint8_t retxor = buff[4];

        if (retlen != wr || retxor != xors) {
            throw "rd check";
        }
    }

    free(buff);
}

void datagram_test_server(bb_dev_handle_t* pdh)
{
    int      packlen = 1024 * 1024;
    uint8_t* buff    = (uint8_t*)malloc(1024 * 1024);

    int fd = bb_socket_open(pdh, BB_SLOT_0, 1, BB_SOCK_FLAG_RX | BB_SOCK_FLAG_TX | BB_SOCK_FLAG_DATAGRAM, NULL);

    if (fd < 0) {
        throw "fd";
    }

    while (1) {

        int rd = bb_socket_read(fd, buff, packlen, -1);

        uint8_t xors  = xor_check(buff, rd);
        int32_t wrlen = rd;

        printf("get len = %d , xor = %#x\n", rd, xors);

        uint8_t wrbuf[5];
        memcpy(wrbuf, &wrlen, 4);
        wrbuf[4] = xors;

        int wr = bb_socket_write(fd, wrbuf, 5, -1);

        if (wr != 5) {
            throw "len";
        }
    }

    free(buff);
}

void bb_event_hotplug(void* arg, void* usr)
{
    bb_event_hotplug_t* plug = (bb_event_hotplug_t*)arg;

    printf("dev hotplug cb id = %d mac = %s status = %s\n", plug->id, plug->bb_mac.mac, plug->status ? "on" : "off");
}

int main()
{
    bb_host_t* phost;

    int ret = bb_host_connect(&phost, "127.0.0.1", BB_PORT_DEFAULT);

    if (ret) {
        printf("connect failed = %d\n", ret);
        exit(-1);
    }

    bb_dev_t** devs;

    int sz = bb_dev_getlist(phost, &devs);
    if (sz <= 0) {
        printf("dev cnt = 0\n");
        exit(-1);
    }

    bb_dev_reg_hotplug_cb(phost, bb_event_hotplug, NULL);

    bb_dev_info_t dev_info;
    bb_dev_getinfo(devs[0], &dev_info);

    bb_dev_handle_t* hbb = bb_dev_open(devs[0]);

    if (ret < 0) {
        printf("bb_dev_probe err\n");
        return -1;
    }

    test_bb_get_status(hbb);

    callback_test(hbb);

// plot 测试
#if 0
    set_plot(pdh);
#endif

    // socket_echo_test_client(hbb);

    // socket_echo_test_server(hbb);

    socket_speed_test(hbb);

    // datagram_test_client(hbb);

    bb_dev_close(hbb);

    bb_dev_freelist(devs);

    bb_host_disconnect(phost);

    return 0;
}
